# coding: utf-8

import math

import CurvePath
import CurveEditorBoilerplate as cep
import Pcl4Curve as pc
from PSOFT.vendor.Qt_py.Qt import QtCore, QtGui, QtWidgets


class Context():
    DragMode_None = 0
    DragMode_SelRect = 1
    DragMode_Move = 2
    DragMode_Handle1 = 3
    DragMode_Handle2 = 4
    DragMode_Cpoy = 5


class ControlPoint(QtWidgets.QGraphicsItem):

    def __init__(self):
        super(ControlPoint, self).__init__()
        self.__pen = QtGui.QPen(QtGui.QColor(240, 240, 240, 255))
        self.__ppen = QtGui.QPen(QtGui.QColor(189, 189, 189))
        self.__dpen = QtGui.QPen(QtGui.QColor(240, 240, 240, 255))
        self.__dpen.setStyle(QtCore.Qt.DashLine)
        self.__penNotActive = QtGui.QPen(QtGui.QColor(73, 73, 73, 181))
        self.__brush = QtGui.QBrush(QtGui.QColor(43, 43, 43, 255))
        self.__brushActive = QtGui.QBrush(QtGui.QColor(189, 189, 189))
        self.__brushNotActive = QtGui.QBrush(QtGui.QColor(73, 73, 73, 181))

        self.model = pc.ControlPointModels()
        self.setPos(cep.VIEW_MERGIN_X, cep.VIEW_MERGIN_BOTTOM)

        self.referencePoints = {}
        self.dragMode = Context.DragMode_None
        self.selectangle = QtCore.QRectF(0, 0, 0, 0)

        self.stroker = QtGui.QPainterPathStroker()
        self.stroker.setWidth(1)
        self.bezierCurve = CurvePath.BezierCurve()
        self.viewWidth = cep.VIEW_WIDTH

    def type(self):
        return QtWidgets.QGraphicsItem.UserType + 2

    def boundingRect(self):
        return QtCore.QRectF(
            -cep.VIEW_MERGIN_X,
            -cep.VIEW_MERGIN_BOTTOM,
            self.viewWidth+cep.VIEW_MERGIN_X*2,
            cep.VIEW_HEIGHT+cep.VIEW_MERGIN_TOP+cep.VIEW_MERGIN_BOTTOM)

    def toScreenPos(self, point):
        return pc.Point(point.x * self.viewWidth, point.y * cep.VIEW_HEIGHT)

    def paint(self, painter, option, widget):

        if self.dragMode == Context.DragMode_SelRect:
            painter.setPen(self.__dpen)
            painter.drawRect(self.selectangle)

        painter.setPen(self.__pen)
        curve = self.bezierCurve.drawCurve(
            self.model, self.viewWidth, cep.VIEW_HEIGHT)
        if curve:
            painter.drawPath(curve)

        for curr, prev, fure in self.model.iter():
            if curr.cp.x < 0:
                continue

            cp = self.toScreenPos(curr.cp)
            if curr.selected:
                painter.setPen(self.__ppen)
                painter.setBrush(self.__brushActive)
                if curr.handleType in {pc.HANDLE_BEZIER, pc.HANDLE_BREAK}:
                    h1 = self.toScreenPos(curr.getModifiedHandleIn(prev))
                    h2 = self.toScreenPos(curr.getModifiedHandleOut(fure))
                    painter.setPen(self.__pen)
                    painter.drawLine(h1.x, h1.y, cp.x, cp.y)
                    painter.drawLine(h2.x, h2.y, cp.x, cp.y)
                    painter.setPen(self.__ppen)
                    painter.drawEllipse(h1.x-3, h1.y-3, 6, 6)
                    painter.drawEllipse(h2.x-3, h2.y-3, 6, 6)
                painter.drawRect(cp.x-3, cp.y-3, 6, 6)
            else:
                painter.setPen(self.__pen)
                painter.setBrush(self.__brush)
                painter.drawRect(cp.x-3, cp.y-3, 6, 6)

        if not self.isEnabled():
            painter.setPen(self.__penNotActive)
            painter.setBrush(self.__brushNotActive)
            painter.drawRect(self.boundingRect())

    def itemChange(self, change, value):
        return super(ControlPoint, self).itemChange(change, value)

    def mousePressEvent(self, event):
        if not event.button() == QtCore.Qt.LeftButton:
            event.ignore()
            return

        cursolPos = self.mapFromScene(event.scenePos())

        def hitTest(modelPos):
            scPos = self.toScreenPos(modelPos)
            hitrect = QtCore.QRectF(scPos.x-10, scPos.y-10, 20, 20)
            return hitrect.contains(cursolPos)

        def evalHandle():
            for curr, prev, fure in self.model.iter():
                if curr.selected \
                   and curr.handleType in {pc.HANDLE_BEZIER, pc.HANDLE_BREAK}:
                    if hitTest(curr.getModifiedHandleIn(prev)):
                        self.model.deselect()
                        curr.selected = True
                        self.dragMode = Context.DragMode_Handle1
                        return True
                    elif hitTest(curr.getModifiedHandleOut(fure)):
                        self.model.deselect()
                        curr.selected = True
                        self.dragMode = Context.DragMode_Handle2
                        return True
            return False

        def evalPoint():
            for e in reversed([i for i in self.model]):
                if hitTest(e.cp):
                    if event.modifiers() == QtCore.Qt.ControlModifier:  # +Ctrl
                        e.selected = not e.selected
                    else:
                        if not e.selected:
                            self.model.deselect()
                        e.selected = True
                    self.dragMode = Context.DragMode_Move
                    return True
            return False

        def evalCurve():
            if self.bezierCurve.hitTest(self.model,
                                        self.viewWidth,
                                        cep.VIEW_HEIGHT,
                                        cursolPos):
                self.model.deselect()
                curve = self.bezierCurve.getCurve(self.model)
                x = cursolPos.x()/self.viewWidth
                y = cursolPos.y()/cep.VIEW_HEIGHT
                x = pc.clamp(x, 0, 1)
                y = pc.clamp(y, 0, 1)
                dx, dy = (0.1, 0.0)
                if curve.rowCount():
                    y = curve.getValue(x)
                    th = curve.getSlope(x)
                    dx = 0.1 * math.cos(th)
                    dy = 0.1 * math.sin(th)

                pt = self.model.addPoint(-1, (2, x-dx, y-dy, x, y, x+dx, y+dy))

                self.scene().cpCreatedCallback(pt)
                pt.selected = True
                self.dragMode = Context.DragMode_Move
                return True
            return False

        self.dragMode = Context.DragMode_None
        if evalHandle():
            pass
        elif evalPoint():
            pass
        elif evalCurve():
            pass
        else:
            if not event.modifiers() == QtCore.Qt.ControlModifier:  # +Ctrl
                self.model.deselect()
            self.dragMode = Context.DragMode_SelRect

        self.referencePoints = {}
        movableRect = QtCore.QRectF(0, 0, 0, 0)
        for e in self.model:
            self.referencePoints[e.mayaIndex] = e.copy()
            if e.selected:
                movableRect = movableRect.united(
                    QtCore.QRectF(e.cp.x, e.cp.y, 1e-9, 1e-9))

        self.Rommin = pc.Point(-movableRect.left(), -movableRect.top())
        self.Rommax = pc.Point(1.0-movableRect.right(),
                               1.0-movableRect.bottom())
        self.selectangle = QtCore.QRectF(
            event.scenePos().x(), event.scenePos().y(), 0, 0)

        self.recalcSpline()
        self.setSelectedXY()
        self.setSymButton()
        self.update()
        event.accept()

    def mouseMoveEvent(self, event):

        mouseDownPos = event.buttonDownPos(QtCore.Qt.LeftButton)
        scenePoint = self.mapFromScene(event.scenePos())

        if self.dragMode == Context.DragMode_SelRect:
            if not event.modifiers() == QtCore.Qt.ControlModifier:  # +Ctrl
                self.selectangle = QtCore.QRectF(
                    mouseDownPos.x(), mouseDownPos.y(), 1e-9, 1e-9)
            self.selectangle = self.selectangle.united(
                QtCore.QRectF(scenePoint.x(), scenePoint.y(), 1e-9, 1e-9))

            for e in self.model:
                if self.selectangle.contains(*self.toScreenPos(e.cp)):
                    e.selected = True
                elif not event.modifiers() == QtCore.Qt.ControlModifier:
                    e.selected = False

        elif self.dragMode == Context.DragMode_Move:

            delta = pc.Point((scenePoint.x()-mouseDownPos.x())/self.viewWidth,
                             (scenePoint.y()-mouseDownPos.y())/cep.VIEW_HEIGHT)

            if self.dragMode == Context.DragMode_Move:
                delta = delta.clamp(self.Rommin, self.Rommax)

            for e in self.model:
                try:
                    base = self.referencePoints[e.mayaIndex]
                    if not base.selected:
                        continue
                except KeyError:
                    continue
                if event.scenePos().y() < -10.0:
                    e.cp = e.h1 = e.h2 = pc.Point(-1, -1)
                else:
                    e.cp = base.cp + delta
                    e.h1 = base.h1 + delta
                    e.h2 = base.h2 + delta

        elif self.dragMode in {Context.DragMode_Handle1,
                               Context.DragMode_Handle2}:
            delta = pc.Point((scenePoint.x()-mouseDownPos.x())/self.viewWidth,
                             (scenePoint.y()-mouseDownPos.y())/cep.VIEW_HEIGHT)

            for e in self.model:
                try:
                    base = self.referencePoints[e.mayaIndex]
                    if not base.selected:
                        continue
                except KeyError:
                    continue
                if self.dragMode == Context.DragMode_Handle1:
                    e.h1 = base.h1 + delta
                    e.h1 = e.getModifiedHandleIn(None)
                    if e.handleType == pc.HANDLE_BEZIER:
                        e.h2 = 2*e.cp - e.h1
                elif self.dragMode == Context.DragMode_Handle2:
                    e.h2 = base.h2 + delta
                    e.h2 = e.getModifiedHandleOut(None)
                    if e.handleType == pc.HANDLE_BEZIER:
                        e.h1 = 2*e.cp - e.h2

        self.model.sort()
        self.recalcSpline()
        self.setSelectedXY()
        self.setSymButton()
        self.update()
        event.accept()

    def mouseReleaseEvent(self, event):

        self.dragMode = Context.DragMode_None
        self.selectangle = QtCore.QRectF(0, 0, 0, 0)

        self.recalcSpline()
        removedItems = []
        editedItems = []
        for key, val in self.referencePoints.items():
            node = self.model.getPointbyMayaIndex(key)
            if node and not val == node:
                if node.cp.x < 0.0:
                    removedItems.append(node)
                else:
                    editedItems.append(node)

        self.scene().cpDestroyedCallback(removedItems)
        self.model.remove(removedItems)
        self.scene().cpChangedCallback(editedItems)

        self.referencePoints = {}

        self.setSelectedXY()
        self.setSymButton()
        self.update()
        event.accept()

    def recalcSpline(self):
        self.model.sort()
        for curr, prev, fure in self.model.iter():
            if not curr.handleType == pc.HANDLE_SPLINE:
                continue

            if not prev or not fure:
                curr.h1 = pc.Point(*curr.cp)
                curr.h2 = pc.Point(*curr.cp)
            else:
                curr.autoBezier(prev, fure)

    def reverse(self):
        for e in self.model:
            e.cp = pc.Point(1.0 - e.cp.x, e.cp.y)
            e.h1 = pc.Point(1.0 - e.h1.x, e.h1.y)
            e.h2 = pc.Point(1.0 - e.h2.x, e.h2.y)
            e.h1, e.h2 = e.h2, e.h1

        self.model.sort()
        self.scene().cpChangedCallback(self.model)
        self.setSelectedXY()
        self.setSymButton()

    def symmetrize(self):

        D = lambda _ :  False
        for e in self.model.selection():
            if e.cp.x < 0.5:
                D = lambda e : 0.5 < e.x
                break
            elif 0.5 < e.cp.x:
                D = lambda e : e.x < 0.5
                break
        else:
            return

        symmetryItems = []
        removedItems = []
        for e in [i for i in self.model]:
            if e.cp.x == 0.5:
                if e.handleType in {pc.HANDLE_LINEAR, pc.HANDLE_SPLINE}:
                    continue

                if e.handleType == pc.HANDLE_BEZIER:
                    e.handleType = pc.HANDLE_BREAK

                if D(e.h1):
                    e.h1 = pc.Point(1.0 - e.h2.x, e.h2.y)
                elif D(e.h2):
                    e.h2 = pc.Point(1.0 - e.h1.x, e.h1.y)
                continue
            if D(e.cp):
                removedItems.append(e)
            else:
                cp = self.model.addPoint(-1, e.getMirror())

                cp.selected = e.selected
                symmetryItems.append(cp)

        self.model.remove(removedItems)
        self.scene().cpDestroyedCallback(removedItems)
        self.scene().cpCreatedCallback(symmetryItems)

        self.model.sort()
        self.scene().cpChangedCallback(self.model)
        self.setSelectedXY()
        self.setSymButton()


    def setHandleType(self, handleType):
        if handleType == pc.HANDLE_LINEAR:
            for e in self.model.selection():
                e.handleType = pc.HANDLE_LINEAR

        elif handleType == pc.HANDLE_SPLINE:
            for e in self.model.selection():
                e.handleType = pc.HANDLE_SPLINE

        elif handleType == pc.HANDLE_BEZIER:
            curve = self.bezierCurve.getCurve(self.model)

            for e, e1, e2 in self.model.iter():
                if not e.selected or e.handleType == handleType:
                    continue

                if not e1:
                    if e.handleType in {pc.HANDLE_LINEAR, pc.HANDLE_SPLINE}:
                        x = e.cp.x*0.706 + e2.cp.x*0.294
                        e.h2 = pc.Point(x, curve.getValue(x))
                    e.h1 = 2*e.cp - e.h2
                elif not e2:
                    if e.handleType in {pc.HANDLE_LINEAR, pc.HANDLE_SPLINE}:
                        x = e.cp.x*0.706 + e1.cp.x*0.294
                        e.h1 = pc.Point(x, curve.getValue(x))
                    e.h2 = 2*e.cp - e.h1
                elif e.handleType == pc.HANDLE_LINEAR:
                    th1 = curve.getSlope(e.cp.x-1e-3)
                    th2 = curve.getSlope(e.cp.x+1e-3)
                    th = 0.5 * (th1 + th2)
                    e.h1 = pc.Point(e.cp.x - 0.1*math.cos(th),
                                    e.cp.y - 0.1*math.sin(th))
                    e.h2 = 2*e.cp - e.h1
                elif e.handleType == pc.HANDLE_SPLINE:
                    e.h1 = e.cp+(e.h1-e.h2)*0.5
                    e.h2 = 2*e.cp - e.h1
                elif e.handleType == pc.HANDLE_BREAK:
                    e.h2 = 2*e.cp - e.h1

                e.handleType = handleType

        elif handleType == pc.HANDLE_BREAK:
            curve = self.bezierCurve.getCurve(self.model)

            for e, e1, e2 in self.model.iter():
                if not e.selected or e.handleType == handleType:
                    continue

                if not e1 and e.handleType in {pc.HANDLE_LINEAR,
                                               pc.HANDLE_SPLINE}:
                    x = e.cp.x*0.706 + e2.cp.x*0.294
                    e.h2 = pc.Point(x, curve.getValue(x))
                    e.h1 = pc.Point(e.x - 0.1, e.y)

                elif not e2 and e.handleType in {pc.HANDLE_LINEAR,
                                                 pc.HANDLE_SPLINE}:
                    x = e.cp.x*0.706 + e1.cp.x*0.294
                    e.h1 = pc.Point(x, curve.getValue(x))
                    e.h2 = pc.Point(e.x + 0.1, e.y)

                elif e.handleType == pc.HANDLE_LINEAR:
                    th1 = curve.getSlope(e.cp.x-1e-3)
                    th2 = curve.getSlope(e.cp.x+1e-3)
                    e.h1 = pc.Point(
                            e.cp.x - 0.1*math.cos(th1),
                            e.cp.y - 0.1*math.sin(th1)
                            )
                    e.h2 = pc.Point(
                            e.cp.x + 0.1*math.cos(th2),
                            e.cp.y + 0.1*math.sin(th2)
                            )

                e.handleType = handleType

        self.recalcSpline()
        self.scene().cpChangedCallback([e for e in self.model if e.selected
                                        or e.handleType == pc.HANDLE_SPLINE])

        self.update()

    def setSelectedXY(self):
        sel = [e for e in self.model.selection()]
        if not sel:
            self.scene().xyTextUpdate.emit(-1, -1)
        elif len(sel) == 1:
            self.scene().xyTextUpdate.emit(sel[0].cp.x, sel[0].cp.y)
        else:
            x_ = sel[0].cp.x
            y_ = sel[0].cp.y
            for e in sel:
                if e.cp.x != x_:
                    xval = -1
                    break
            else:
                xval = x_

            for e in sel:
                if e.cp.y != y_:
                    yval = -1
                    break
            else:
                yval = y_

            self.scene().xyTextUpdate.emit(xval, yval)

    def setSymButton(self):

        sel = [e.cp.x for e in self.model.selection()]
        if sel and (0.5 < sel[0] or sel[-1] < 0.5):
            self.scene().symButtonUpdate.emit(True)
        else:
            self.scene().symButtonUpdate.emit(False)


    @QtCore.Slot(float)
    def setControlPointX(self, xval):
        sel = [e for e in self.model.selection()]
        editedItems = []
        if sel:
            for e in sel:
                if xval != e.cp.x:
                    delta = pc.Point(xval - e.cp.x, 0)
                    e.cp = e.cp + delta
                    e.h1 = e.h1 + delta
                    e.h2 = e.h2 + delta
                    editedItems.append(e)

        self.scene().cpChangedCallback(editedItems)

    @QtCore.Slot(float)
    def setControlPointY(self, yval):
        sel = [e for e in self.model.selection()]
        editedItems = []
        if sel:
            for e in sel:
                if yval != e.cp.y:
                    delta = pc.Point(0, yval - e.cp.y)
                    e.cp = e.cp + delta
                    e.h1 = e.h1 + delta
                    e.h2 = e.h2 + delta
                    editedItems.append(e)

        self.scene().cpChangedCallback(editedItems)
